#include "helper.h"
#include "monitor.h"
#include "reg.h"
#include "goldentrace.h"

extern uint32_t instr;
extern char assembly[80];

/* decode R-type instrucion */
static void decode_r_type(uint32_t instr) {

	//rs
	op_src1->type = OP_TYPE_REG;
	op_src1->reg = (instr & RS_MASK) >> (RT_SIZE + IMM_SIZE);
	op_src1->val = reg_w(op_src1->reg);

	//rt	
	op_src2->type = OP_TYPE_REG;
	op_src2->reg = (instr & RT_MASK) >> (RD_SIZE + SHAMT_SIZE + FUNC_SIZE);
	op_src2->val = reg_w(op_src2->reg);

	//rd
	op_dest->type = OP_TYPE_REG;
	op_dest->reg = (instr & RD_MASK) >> (SHAMT_SIZE + FUNC_SIZE);

	//sa
	shamt->type = OP_TYPE_IMM;
	shamt->imm = (instr & SHAMT_MASK) >> (FUNC_SIZE);
	shamt->val = shamt->imm;
}


//******************************************************************************************************************************

make_helper(add) {

	decode_r_type(instr);
	int32_t sign1 = ((op_src1->val) & 0x80000000) >> 31;
	int32_t sign2 = ((op_src2->val) & 0x80000000) >> 31;
	int32_t temp = (op_src1->val + op_src2->val);
	int32_t temp_sign = (temp & 0x80000000) >> 31;

	if ( (sign1 == sign2) && temp_sign != sign1) {

		uint32_t temp = cp0.Cause & ~(0x8000007c);
		temp |= (0x0c << 2);
		cp0.Cause = temp;

		uint32_t tempp = cp0.Status & ~(0x00000002);
		tempp |= 0x2;
		cp0.Status = tempp;


		cp0.EPC = cpu.pc;
		printf("add overflow\n");
		cpu.pc = 0xbfc00380;
		
	}

	else {
		reg_w(op_dest->reg) = temp;
		sprintf(assembly, "add   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
		fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
	}
	
}

make_helper(addu) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = (op_src1->val + op_src2->val);
	sprintf(assembly, "addu   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(sub) {

	decode_r_type(instr);

	int32_t sign1 = ((op_src1->val) & 0x80000000) >> 31;
	int32_t sign2 = ((op_src2->val) & 0x80000000) >> 31;
	int32_t temp = (op_src1->val - op_src2->val);
	int32_t temp_sign = (temp & 0x80000000) >> 31;

	if ( (sign1 != sign2) && temp_sign != sign1) {
		
		//overflow

		uint32_t temp = cp0.Cause & ~(0x8000007c);
		temp |= (0x0c << 2);
		cp0.Cause = temp;

		uint32_t tempp = cp0.Status & ~(0x00000002);
		tempp |= 0x2;
		cp0.Status = tempp;


		cp0.EPC = cpu.pc;
		printf("sub overflow\n");
		cpu.pc = 0xbfc00380;
		
	}

	else {
		reg_w(op_dest->reg) = temp;
		sprintf(assembly, "sub   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
		fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
	}

}

make_helper(subu) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = (op_src1->val - op_src2->val);
	sprintf(assembly, "subu   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(slt) {

	decode_r_type(instr);

	if(op_src1->val < op_src2->val)
		reg_w(op_dest->reg) = 0x1;
	else
		reg_w(op_dest->reg) = 0x0;

	sprintf(assembly, "slt   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(sltu) {

	decode_r_type(instr);

	if((unsigned) op_src1->val < (unsigned) op_src2->val)
		reg_w(op_dest->reg) = 0x1;
	else
		reg_w(op_dest->reg) = 0x0;

	sprintf(assembly, "sltu   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));	
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(div) {

	decode_r_type(instr);
	cpu.lo = (op_src1->val / op_src2->val);
	cpu.hi = (op_src1->val % op_src2->val);
	sprintf(assembly, "div   %s,   %s", REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	fprintf(GoldenTrace, "%08x\t\tHI\t\t\t\t%08x\n", cpu.pc, cpu.hi);	
	fprintf(GoldenTrace, "%08x\t\tLO\t\t\t\t%08x\n", cpu.pc, cpu.lo);
}

make_helper(divu) {

	decode_r_type(instr);
	cpu.lo = ((unsigned)op_src1->val / (unsigned)op_src2->val);
	cpu.hi = ((unsigned)op_src1->val % (unsigned)op_src2->val);
	sprintf(assembly, "divu   %s,   %s", REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));	
	fprintf(GoldenTrace, "%08x\t\tHI\t\t\t\t%08x\n", cpu.pc, cpu.hi);	
	fprintf(GoldenTrace, "%08x\t\tLO\t\t\t\t%08x\n", cpu.pc, cpu.lo);
}

make_helper(mult) {

	decode_r_type(instr);

	int64_t prod = (int64_t)op_src1->val * (int64_t)op_src2->val;
	cpu.lo = prod & 0xffffffff;
	cpu.hi = (prod >> 32) & 0xffffffff;
	sprintf(assembly, "mult   %s,   %s", REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));	

	fprintf(GoldenTrace, "%08x\t\tHI\t\t\t\t%08x\n", cpu.pc, cpu.hi);	
	fprintf(GoldenTrace, "%08x\t\tLO\t\t\t\t%08x\n", cpu.pc, cpu.lo);

}

make_helper(multu) {

	decode_r_type(instr);
	uint64_t prod = (uint64_t) op_src1->val * (uint64_t) op_src2->val;
	cpu.lo = prod & 0xffffffff;
	cpu.hi = (prod >> 32) & 0xffffffff;
	sprintf(assembly, "multu   %s,   %s", REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));

	fprintf(GoldenTrace, "%08x\t\tHI\t\t\t\t%08x\n", cpu.pc, cpu.hi);	
	fprintf(GoldenTrace, "%08x\t\tLO\t\t\t\t%08x\n", cpu.pc, cpu.lo);

}

make_helper(and) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = (op_src1->val & op_src2->val);
	sprintf(assembly, "and   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(nor) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = ~(op_src1->val | op_src2->val);
	sprintf(assembly, "nor   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(or) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = (op_src1->val | op_src2->val);
	sprintf(assembly, "or   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(xor) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = (op_src1->val ^ op_src2->val);
	sprintf(assembly, "xor   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), REG_NAME(op_src2->reg));
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(sllv) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = (op_src2->val << op_src1->val);
	sprintf(assembly, "sllv   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg), REG_NAME(op_src1->reg));
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(sll) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = (op_src2->val << shamt->val);
	sprintf(assembly, "sll   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg), shamt->val);
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(srav) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = ((signed)op_src2->val >> op_src1->val);
	sprintf(assembly, "srav   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg), REG_NAME(op_src1->reg));
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(sra) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = ((signed)op_src2->val >> shamt->val);
	sprintf(assembly, "sra   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg), shamt->val);
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(srlv) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = (op_src2->val >> op_src1->val);
	sprintf(assembly, "srlv   %s,   %s,   %s", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg), REG_NAME(op_src1->reg));
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(srl) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = (op_src2->val >> shamt->val);
	sprintf(assembly, "srl   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src2->reg), shamt->val);
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(jr) {

	decode_r_type(instr);
	int32_t temp = op_src1->val;
	cpu.pc = temp - 0x00000004;
	sprintf(assembly, "jr   %s", REG_NAME(op_src1->reg));
}

make_helper(jalr) {

	decode_r_type(instr);
	int32_t temp = op_src1->val;
	reg_w(op_dest->reg) = cpu.pc + 8;
	cpu.pc = temp - 0x00000004;
	sprintf(assembly, "jalr   %s,   %s", REG_NAME(op_dest->reg),REG_NAME(op_src1->reg));
}

make_helper(mfhi) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = cpu.hi;
	sprintf(assembly, "mfhi   %s", REG_NAME(op_dest->reg));
	fprintf(GoldenTrace, "%08x\t\t%d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(mflo) {

	decode_r_type(instr);
	reg_w(op_dest->reg) = cpu.lo;
	sprintf(assembly, "mflo   %s", REG_NAME(op_dest->reg));
	fprintf(GoldenTrace, "%08x\t\t%02d\t\t\t\t%08x\n", cpu.pc, op_dest->reg, reg_w(op_dest->reg));
}

make_helper(mthi) {

	decode_r_type(instr);
	cpu.hi = reg_w(op_src1->reg);
	sprintf(assembly, "mthi   %s", REG_NAME(op_src1->reg));
	fprintf(GoldenTrace, "%08x\t\tHI\t\t\t\t%08x\n", cpu.pc, cpu.hi);
}

make_helper(mtlo) {

	decode_r_type(instr);
	cpu.lo = reg_w(op_src1->reg);
	sprintf(assembly, "mtlo   %s", REG_NAME(op_src1->reg));
	fprintf(GoldenTrace, "%08x\t\tLO\t\t\t\t%08x\n", cpu.pc, cpu.lo);
}


make_helper(eret) {

	decode_r_type(instr);
	cpu.pc = cp0.EPC;
	cp0.Status &= 0xfffffffd;
	memset(cpu.gpr, 0, sizeof(cpu.gpr));
	sprintf(assembly, "eret");

}

make_helper(mfc0) {

	decode_r_type(instr);
	uint32_t temp = cp0.cp0reg[op_dest->reg]._32;
	reg_w(op_src2->reg) = temp;
	sprintf(assembly, "mfc0   %s   %s   0x%x", REG_NAME(op_src2->reg), CP0_NAME(op_dest->reg), instr& 0x00000007);

}

make_helper(mtc0) {

	decode_r_type(instr);
	cp0.cp0reg[op_dest->reg]._32 = reg_w(op_src2->reg);
	sprintf(assembly, "mtc0   %s   %s   0x%x", REG_NAME(op_src2->reg), CP0_NAME(op_dest->reg), instr& 0x00000007);
}

make_helper(BREAK) {

	uint32_t temp = cp0.Cause & ~(0x8000007c);
	temp |= (0x09 << 2);
	cp0.Cause = temp;

	uint32_t tempp = cp0.Status & ~(0x00000002);
	tempp |= 0x2;
	cp0.Status = tempp;
	
	cp0.EPC = cpu.pc;
	cpu.pc = 0xbfc00380;
	sprintf(assembly, "break");
	printf("BREAK!\n");
}

make_helper(syscall) {
	
	uint32_t temp = cp0.Cause & ~(0x8000007c);
	temp |= (0x08 << 2);
	cp0.Cause = temp;

	uint32_t tempp = cp0.Status & ~(0x00000002);
	tempp |= 0x2;
	cp0.Status = tempp;

	cp0.EPC = cpu.pc;
	cpu.pc = 0xbfc00380;
	sprintf(assembly, "syscall");
	printf("SYSCALL!\n");
}

make_helper(privilege) {

	if(((instr&0x03e00000) >> 20) == 0x00000000)
		mfc0(cpu.pc);
	else if(((instr&0x03e00000) >> 23) == 0x00000001)
		mtc0(cpu.pc);
	else if(instr&0x02000000)
		eret(cpu.pc);
}